#include "Lily_help.h"

Li_List __li_events__ = NULL;
static Li_List __created_names__ = NULL;

static void _signal_do_cmd_delegate(void *private_data, void *event_data)
{
	const char *cmd = (const char *)private_data;
	const char *arg = (const char *)event_data;
	char tx[256];
	if (strlen(arg) + strlen(cmd) > sizeof(tx) - 2)
	{
		return;
	}
	sprintf(tx, "%s %s", cmd, arg);
	lily_do(tx);
}

Broadcast li_new_event(const char *id)
{
	if (__li_events__ == NULL)
	{
		__li_events__ = new_li(Broadcast_t);
		if (!__li_events__)
			return NULL;
	}
	Broadcast bp = li_get_event(id);
	if (bp)
		return bp;
	Broadcast_t b = {.name = id, .hashCode = 0, .receivers = NULL};
	int ind;
	if (ind = public_obj(__li_events__, &b) < 0)
		return NULL;
	return (Broadcast)list_index(__li_events__, ind);
}
Broadcast li_get_event(const char *id)
{
	if (!__li_events__)
		return NULL;
	int ind = search_obj(__li_events__, id);
	if (ind < 0)
		return NULL;
	return (Broadcast)list_index(__li_events__, ind);
}
void li_remove_event(const char *id)
{
	if (!__li_events__)
		return;
	int ind = search_obj(__li_events__, id);
	if (ind < 0)
		return;
	Broadcast b = list_content(__li_events__, Broadcast_t);
	if (b[ind].receivers)
	{
		if (__created_names__)
		{
			char **_names_ = list_content(__created_names__, char *);
			int n = b[ind].receivers->count;
			_LiReceiver rs = list_content(b[ind].receivers, _LiReceiver_t);
			for (int i = 0; i < n; i++)
			{
				if (rs[i].f != _signal_do_cmd_delegate)
					continue;
				for (int j = 0; j < __created_names__->count; j++)
				{
					if (_names_[j] == rs[i].private_data)
					{
						free(_names_[j]);
						list_swap_out(__created_names__, j);
						list_reduction(__created_names__);
						break;
					}
				}
			}
		}
		delete_list(b[ind].receivers);
	}
	list_remove_at(__li_events__, ind);
}

void event_broadcast_from(Broadcast b, void *event_data)
{
	if (!b)
		return;
	Li_List receivers = b->receivers;
	if (!receivers)
		return;
	int n = receivers->count;
	_LiReceiver rs = list_content(receivers, _LiReceiver_t);
	for (int i = 0; i < n; i++)
	{
		if (!rs[i].f)
			continue;
		rs[i].f(rs[i].private_data, event_data);
	}
}
void event_broadcast_from_tag(const char *name, void *event_data)
{
	Broadcast b = li_get_event(name);
	if (!b)
		return;
	event_broadcast_from(b, event_data);
}

int event_register_to(Broadcast b, void *private_data, LiEventListener f)
{
	if (b->receivers == NULL)
		b->receivers = new_li(_LiReceiver_t);
	if (!b->receivers)
		return -1;
	_LiReceiver_t r = {private_data, f};
	if (li_add(b->receivers, r) < 0)
		return -1;
	return 0;
}
int event_register_to_tag(const char *name, void *private_data, LiEventListener f)
{
	Broadcast b;
	int ind = search_obj(__li_events__, name);
	if (ind < 0)
	{
		b = li_new_event(name);
		if (!b)
			return -1;
	}
	else
	{
		b = (Broadcast)list_index(__li_events__, ind);
	}
	return event_register_to(b, private_data, f);
}

void event_deregister_from(Broadcast b, void *private_data, LiEventListener f)
{
	if (!b->receivers)
		return;
	_LiReceiver rs = list_content(b->receivers, _LiReceiver_t);
	int n = b->receivers->count;
	for (int i = 0; i < n; i++)
	{
		if (rs[i].private_data == private_data && rs[i].f == f)
		{
			list_remove_at(b->receivers, i);
			return;
		}
	}
}

const char *cmd_signal_doc = "signal event:\n"
							 "signal event [data]: signal an event\n"
							 " -h: see help\n"
							 " -l event cmd: listen to an event\n"
							 " -s[d] [event]: show [detail of] events\n"
							 " -n newEvent : new event\n"
							 " -r event [cmdIndex]: remove an event [on a cmd listener]\n";

int cmd_signal(int n, char *args[])
{

	if (n <= 1 || strcmp(args[1], "-h") == 0)
	{
		lily_out(cmd_signal_doc);
		return 0;
	}
	if (strcmp(args[1], "-s") == 0 || strcmp(args[1], "-sd") == 0)
	{
		if (!__li_events__)
		{
			return 0;
		}
		if (n > 2)
		{
			Broadcast b = li_get_event(args[2]);
			if (!b)
			{
				lily_out("event not found!");
				return 0;
			}
			lily_outf("%s-%d\n", b->name, b->receivers ? b->receivers->count : 0);
			if (args[1][2] == 'd')
			{
				if (!b->receivers)
					return 0;
				_LiReceiver rs = list_content(b->receivers, _LiReceiver_t);
				int n = b->receivers->count;
				for (int i = 0; i < n; i++)
				{
					if (rs[i].f == _signal_do_cmd_delegate)
						lily_outf("%d) %s\n", i, (const char *)(rs[i].private_data));
				}
			}
			return 0;
		}
		int n = __li_events__->count;
		if (!n)
			return 0;
		Broadcast b = list_content(__li_events__, Broadcast_t);
		lily_out("event-listenerCount\n");
		for (int i = 0; i < n; i++)
		{
			lily_outf("%s-%d\n", b[i].name, b[i].receivers ? b[i].receivers->count : 0);
			if (args[1][2] == 'd')
			{
				if (!b[i].receivers)
					continue;
				_LiReceiver rs = list_content(b[i].receivers, _LiReceiver_t);
				int n = b[i].receivers->count;
				for (int i = 0; i < n; i++)
				{
					if (rs[i].f == _signal_do_cmd_delegate)
						lily_outf("%d) %s\n", i, (const char *)(rs[i].private_data));
				}
			}
		}
		return 0;
	}
	if (strcmp(args[1], "-n") == 0)
	{
		assert_msg(n > 2, "event name is required!");

		char *s = strdup(args[2]);
		assert_msg(s, "failed to create new event");
		if (!li_new_event(s))
		{
			free(s);
			li_error("failed to create new event", -__LINE__);
		}
		if (!__created_names__)
		{
			__created_names__ = new_li(char *);
			assert_msg(__created_names__, "failed to create new event");
		}
		li_add(__created_names__, s);
		return 0;
	}
	if (strcmp(args[1], "-r") == 0)
	{
		assert_msg(n > 2, "event name is required!");
		if (n == 3)
		{
			if (__created_names__)
			{
				int n = __created_names__->count;
				str *names = list_content(__created_names__, char *);
				for (int i = 0; i < n; i++)
				{
					if (strcmp(names[i], args[2]) == 0)
					{
						li_remove_event(args[2]);
						free(names[i]);
						list_swap_out(__created_names__, i);
						list_reduction(__created_names__);
						return 0;
					}
				}
			}
			li_remove_event(args[2]);
			return 0;
		}
		Broadcast b = li_get_event(args[2]);
		assert_msg(b, "event not found!");
		int index = atoi(args[3]);
		assert_msg(index >= 0 && b->receivers && index < b->receivers->count, "index out of range!");
		_LiReceiver rs = list_content(b->receivers, _LiReceiver_t);
		assert_msg(rs[index].f == _signal_do_cmd_delegate, "not a cmd receiver!");
		free(rs[index].private_data);
		event_deregister_from(b, rs[index].private_data, rs[index].f);
	}
	if (strcmp(args[1], "-l") == 0)
	{
		assert_msg(n > 3, "event name and cmd is required!");
		Broadcast b = li_get_event(args[2]);
		assert_msg(b, "event not found!");
		char *cmd = strdup(args[3]);
		event_register_to(b, cmd, _signal_do_cmd_delegate);
		return 0;
	}
	Broadcast b = li_get_event(args[1]);
	assert_msg(b, "event not found!");
	event_broadcast_from(b, n > 2 ? args[2] : "");
	return 0;
}
static void _on_memory_reduction(void *private_data, void *arg)
{
	if (__li_events__)
	{
		int n = __li_events__->count;
		Broadcast bs = list_content(__li_events__, Broadcast_t);
		for (int i = 0; i < n; i++)
		{
			if (!bs[i].receivers)
				continue;
			list_reduction(bs[i].receivers);
		}
		list_reduction(__li_events__);
	}
	if (__created_names__)
		list_reduction(__created_names__);
}
void signal_init()
{
	public_a_cmd_link_doc("signal", cmd_signal, cmd_signal_doc);
	event_register_to_tag("memReduction", NULL, _on_memory_reduction);
}